package pokeronlab_2014;

/**
 * 
 */

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.nio.ByteBuffer;
import java.sql.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;

import pokeronlab_2014.mcts.BotNode;
import pokeronlab_2014.mcts.GameRules;
import pokeronlab_2014.mcts.OpponentNode;
import pokeronlab_2014.mcts.TreeNode;

import com.biotools.meerkat.Card;

/**
 * Adatbázis osztály, az ágens ismereteit tartalmazza (singleton). <br/>
 * Az openDatabase()/closeDatabase()-t akkor kell hívni, ha a függvény
 * adatbázishoz fér hozzá, és MÁSHONNAN HÍVÓDIK, tehát nem az adatbázisból!
 * egyébként elég a statementet lekérni (tagváltozó)
 * 
 * @author andras
 * 
 */
public class Database {

	/**
	 * Singleton működés miatt privát példány
	 */
	private static Database instance;
	private static Connection connection;
	Statement statement;
	
	HashMap<Integer,ArrayList<SVMClassifierData>> playerPointCache;
	HashMap<Integer,ArrayList<Integer>> playerAnswerCache;
	
	/**
	 * A saját székünk száma (adott játék)
	 */
	int ourSeat;

	/**
	 * A saját lapjaink (adott játék)
	 */
	int c1, c2;

	/**
	 * Az adott játék boardja;
	 */
	int[] board = new int[5];
	/**
	 * Az adott játék ID-je
	 */
	private static int thisGameID;
	/**
	 * A játékosok neveit tartalmazó tömb (adott játékban)
	 */
	String[] playerNames = new String[10];

	HashMap<Integer, ArrayList<SVMClassifierData>[]> playerTrainingPoints;
	HashMap<Integer, ArrayList<Integer>[]> playerTrainingAnswers;
	
	
	/**
	 * Beálítja a szék számát
	 * 
	 * @param ourSeat
	 *            A székünk száma
	 */
	public void setOurSeat(int ourSeat) {
		this.ourSeat = ourSeat;
	}

	/**
	 * @return Singleton példány
	 */
	public static Database getInstance() {		
		if (instance == null)
			instance = new Database();
		return instance;

	}
	
		void createDatabase(){
			
		}
	
	/**
	 * Default konstruktor, privát.
	 */
	public Database() {
		try {
			// SQLite osztály betöltése
			Controller.debugWindow.printMessage("SQLite loading...");
			Class.forName("org.sqlite.JDBC");
			Controller.debugWindow.printMessage("...class loaded!");
		} catch (Exception e) {
			Controller.debugWindow.printMessage(e.getMessage());
		} 
		//ha nem létezik az adatbázis, létrehozzuk
		if(!(new File("agentDatabase.db").isFile())){
			openDatabase();
			try {
				statement = connection.createStatement();
				statement.executeUpdate("create table event(eventID INTEGER"
						+ " PRIMARY KEY AUTOINCREMENT,"
						+ "gameID INTEGER,"
						+ "playerID INTEGER,action INTEGER,stage INTEGER)");
				statement.executeUpdate("create table game(gameID INTEGER"
						+ " PRIMARY KEY AUTOINCREMENT,"
						+ "winnerID INTEGER,"
						+ "winningHand TEXT,tableID INTEGER,buttonID INTEGER"
						+ ",board TEXT)");
				statement.executeUpdate("create table hand(handID INTEGER"
						+ " PRIMARY KEY AUTOINCREMENT,"
						+ "playerID INTEGER,"
						+ "cards TEXT,gameID INTEGER)");
				statement.executeUpdate("create table player(playerID INTEGER"
						+ " PRIMARY KEY AUTOINCREMENT,name TEXT)");
				statement.executeUpdate("create table pokertable(tableID INTEGER"
						+ ",playerID INTEGER,seatNumber INTEGER)");
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} finally {
				closeDatabase();
			}

			try {
				ObjectInputStream is = new ObjectInputStream(new FileInputStream("agentTrainingPoints.ser"));
				
				playerTrainingPoints = (HashMap<Integer, ArrayList<SVMClassifierData>[]>) is.readObject();
				
				is.close();
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			try {
				ObjectInputStream is = new ObjectInputStream(new FileInputStream("agentTrainingAnswers.ser"));
				is.close();
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			 
		}
		
	}

	/**
	 * Megnyitja a kapcsolatot az adatbázissal
	 */
	public void openDatabase() {
		try {
			connection = DriverManager
					.getConnection("jdbc:sqlite:agentDatabase.db");
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Bezárja a kapcsolatot az adatbázissal
	 */
	public void closeDatabase() {
		try {
			connection.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Visszaadja, hogy egy játékos benne van-e már az adatbázisban.
	 * 
	 * @param name
	 *            A keresett játékos neve
	 * @return Hogy létezik-e az adatbázisban
	 */
	private boolean isPlayerExists(String name) {
		try {
			statement = connection.createStatement();
			ResultSet resultSet = statement.executeQuery(String.format(
					"SELECT * from Player WHERE name = '%s'", name));
			return resultSet.next();
		} catch (SQLException e) {
			Controller.debugWindow.printMessage(e.getMessage());
		}
		return true;
	}

	/**
	 * Visszaadja egy játékos ID-jét a neve alapján
	 * 
	 * @param name
	 *            Játékos neve
	 * @return Játékos ID-je
	 */
	private int getIDFromName(String name) {
		try {
			statement = connection.createStatement();
			ResultSet res = statement
					.executeQuery(String.format(
							"select playerID from player where "
									+ "name = '%s'", name));
			return res.getInt(1);
		} catch (SQLException e) {
			Controller.debugWindow.printMessage("error getting ID for " + name);
		}
		return -1;
	}

	/**
	 * @return Információ a szakértői rendszernek
	 */
	ExpertSystemData getExpertSystemData() {
		ExpertSystemData expertSystemData = new ExpertSystemData();
		expertSystemData.fold = 0;
		expertSystemData.check = 0;
		expertSystemData.call = 0;
		expertSystemData.bet = 0;
		expertSystemData.card1 = new Card(c1);
		expertSystemData.card2 = new Card(c2);
		expertSystemData.numPlayers = Controller.thisGameInfo.getNumPlayers();
		expertSystemData.pos = Controller.thisGameInfo.getCurrentPlayerSeat()
				- Controller.thisGameInfo.getBigBlindSeat();
		if (expertSystemData.pos <= 0)
			expertSystemData.pos += 10;
		Controller.debugWindow.printMessage("adatbazis-kezelesig jo");
		Statement statement;
		// adatbázis megnyitása
		openDatabase();
		try {
			statement = connection.createStatement();
			ResultSet resulSet = statement.executeQuery(String.format(
					"select action from event where gameID = %d", thisGameID));
			int i = 0;
			while (resulSet.next()) {
				i++;
				if (Integer.parseInt(resulSet.getString(i)) == 0)
					expertSystemData.fold++;
				if (Integer.parseInt(resulSet.getString(i)) == 1)
					expertSystemData.check++;
				if (Integer.parseInt(resulSet.getString(i)) == 2
						&& Integer.parseInt(resulSet.getString(i)) == 4)
					expertSystemData.call++;
				if (Integer.parseInt(resulSet.getString(i)) == 3)
					expertSystemData.bet++;
			}
		} catch (SQLException e) {
			Controller.debugWindow.printMessage(e.getMessage());
		} finally {
			closeDatabase();
		}
		Controller.debugWindow.printMessage("getexpertsystemdata jo");
		return expertSystemData;
	}

	/**
	 * @return Az MCTS Döntéshozó gyökéreleme, ha még nincs neki
	 */
	public TreeNode getTreeRoot() {
		openDatabase();
		TreeNode treeNode;
		if(Controller.thisGameInfo.getCurrentPlayerSeat()== ourSeat)
			treeNode = new BotNode();
		else
			treeNode = new OpponentNode();
		
		treeNode.SVMbemenet = getSVMClassifierData(thisGameID,getMaxEventID(thisGameID));
		treeNode.rules = new GameRules();
		// jó bugforrás egyébként
		treeNode.rules.playerIDs = treeNode.SVMbemenet.playerIDs;
		treeNode.mypos = ourSeat;
		//lapok beállítása
		treeNode.rules.flop1 = board[0];
		treeNode.rules.flop2 = board[1];
		treeNode.rules.flop3 = board[2];
		treeNode.rules.elsolap = c1;
		treeNode.rules.masodiklap = c2;
		
		treeNode.rules.actualplayer = Controller.thisGameInfo
				.getCurrentPlayerSeat();
		
		//playeringame beállítása
		treeNode.rules.playeringame = new int[10];
		for(int i = 0; i < treeNode.rules.playerIDs.length; i++){
			if(treeNode.rules.playerIDs[i] != 0){
				treeNode.rules.playeringame[i] = 1;
			}
		}
		closeDatabase();
		return treeNode;
	}

	
	private int getMaxEventID(int gameID){
		int maxEventID = 0;
		try {
			statement = connection.createStatement();
			ResultSet playerEvents = statement.executeQuery(String.format(
					"select max(eventID) from event where gameID = %d",gameID));
			playerEvents.next();
			maxEventID = playerEvents.getInt("max(eventID)");
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return maxEventID;
	}
	
	
	private ArrayList<Integer> getPlayerEventIDs(int gameID){
		ArrayList<Integer> playerEventIDs = new ArrayList<Integer>();
		try {
			statement = connection.createStatement();
			ResultSet playerEvents = statement.executeQuery(String.format(
					"select * from event where gameID = %d",gameID));
			while(playerEvents.next()){
				if(playerEvents.getInt("action") <= 4)
					playerEventIDs.add(playerEvents.getInt("eventID"));
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return playerEventIDs;
	}
	
	private ArrayList<Integer[]> getPlayerAnswers(int gameID){
		ArrayList<Integer[]> playerEventIDs = new ArrayList<Integer[]>();
		try {
			statement = connection.createStatement();
			ResultSet playerEvents = statement.executeQuery(String.format(
					"select * from event where gameID = %d",gameID));
			while(playerEvents.next()){
				if(playerEvents.getInt("action") <= 4){
					playerEventIDs.add(new Integer[]{playerEvents.getInt("playerID"),playerEvents.getInt("eventID")});
				}
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return playerEventIDs;
	}
	
	/**
	 * Visszaad egy SVMClassifierDatát egy adott játékról
	 * 
	 * @param maxEventID
	 *            az event ID-je, amit még bele szeretnénk tenni
	 * @param gameID
	 *            a játék ID-je, amiről adatot szeretnénk kérni
	 * @return egy SVMClassifierData
	 */
	private SVMClassifierData getSVMClassifierData(int gameID, int maxEventID) {
		SVMClassifierData data = new SVMClassifierData();
		try {
			statement = connection.createStatement();
			
			ResultSet playerData = statement.executeQuery(String.format(
					"select playerID, seatNumber from pokertable where tableID = (select"
							+ " tableID from game where gameID = %d)", gameID));

			// kiszedjük az ID-ket, és a neveket
			HashMap<Integer, Integer> playerIDs = new HashMap<Integer, Integer>();
			HashMap<Integer, Integer> playerSeats = new HashMap<Integer, Integer>();
			while (playerData.next()) {
				playerIDs.put(playerData.getInt("seatNumber"),
						playerData.getInt("playerID"));
				playerSeats.put(playerData.getInt("playerID"),
						playerData.getInt("seatNumber"));
			}
			// ebben tároljuk el a stage-t majd, az egyszerűség kedvéért
			int stage = 0;
			
			ResultSet playerEvents = statement.executeQuery(String.format(
					"select * from event where gameID = %d", gameID));

			while (playerEvents.next() && playerEvents.getInt("eventID")<=
					maxEventID) {
				int action = playerEvents.getInt("action");
				// mindig tároljuk, így végül az aktuálisat fogj
				stage = playerEvents.getInt("stage");
				switch (action) {
				// ha fold, akkor ok az érték
				case 0:
					break;
				// a check = call, tehát ez is
				case 1:
					break;
				case 2:
					action = 1;
					break;
				// a bet/raise = 2
				case 3:
					action = 2;
					break;
				case 4:
					action = 2;
					break;
				default:
					action = -1;
					break;
				}
				if (action != -1) {
					// a székszámot a playerSeats-ból kérjük le, a playerID
					// alapján!
					data.setPlayerData(playerEvents.getInt("stage"),
							playerSeats.get(playerEvents.getInt("playerID")),
							action);
				}
			}// while ciklus vége egyébként
				// playerID-k beállítása
			int[] IDs = new int[10];
			for (int i = 0; i < IDs.length; i++) {
				if(playerIDs.get(i) != null){ 
					IDs[i] = playerIDs.get(i);
				}
			}
			//A board lekérése
			ResultSet boardRes = statement.executeQuery(String.format("select *"
					+ " from game where gameID = %d",thisGameID));
			boardRes.next();
			String[] boardString =  boardRes.getString("board").split(" ");
			//A BOARD 1-től van indexelve!!!
			int[] board = new int[5];
			for(int i=0; i<5; i++){
				board[i] = Integer.parseInt(boardString[i+1]);
			}
			//TODO a board featureit kiszedni egyébként
			data.boardFeatures = 
					new BoardFeatureExtractor().getFeatures(board);
			data.playerIDs = IDs;
			data.stage = stage;

		} catch (SQLException e) {
			e.printStackTrace();
		}
		return data;
	}

	
	/**
	 * Lekéri hogy egy asztallal hány játék van eltárolva.
	 * 
	 * @param tableID a lekért asztal ID-je
	 * @return az asztallal játszott játékok száma
	 */
	public int getGameCountOfTable(int tableID){
		openDatabase();
		int count=0;
		try {
			statement = connection.createStatement();
			ResultSet gameCount = statement.executeQuery(String.format("select"
					+ " count(*) as playedGames from game where"
					+ " tableID = %d",tableID));
			gameCount.next();
			count = gameCount.getInt("playedGames");
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
		closeDatabase();
		}
		return count;
	}
	
	
	/**
	 * Játékos cselekvésének hozzáadása az eventekhez
	 * 
	 * @param position
	 *            A játékos pozíciója
	 * @param action
	 *            A játékos cselekvése
	 */
	void addPlayerEvent(int position, int action, int stage) {
		openDatabase();
		try {
			statement = connection.createStatement();
			statement.executeUpdate(String.format(
					"insert into event (gameID,playerID,"
							+ "action,stage) values (%d,%d,%d,%d)", thisGameID,
					getIDFromName(playerNames[position]), action, stage));
			// debug üzenet kiírása [játékos] [cselekvés] formában
			// Controller.debugWindow.printMessage(playerNames[position] + " "
			// + Action.getAction(action, 0, 0).toString());
		} catch (SQLException e) {
			Controller.debugWindow.printMessage(e.getMessage());
		} finally {
			closeDatabase();
		}
	}

	/**
	 * Egy játékost ad hozzá az adatbázishoz
	 * 
	 * @param ID
	 *            Hozzáadott játékos ID-je
	 * @param name
	 *            Hozzáadott játékos neve
	 */
	private void addPlayer(String name) {
		try {
			statement = connection.createStatement();
			// csak név elég, az ID autoinkrementálódik
			statement.executeUpdate(String.format("insert into Player"
					+ " (Name)\n" + "values ('%s') ", name));
			// logolás
			Controller.debugWindow.printMessage(String.format("Player \"%s"
					+ "\" added", name));
		} catch (SQLException e) {
			Controller.debugWindow.printMessage("Error adding player!");
		}
	}

	/**
	 * Beállítja az aktuális játék játékosait, és a buttont.
	 * 
	 * @param names
	 *            A játékosnevek listája.
	 * @param button
	 *            A button helye.
	 */
	void newGame(String[] names, int button) {
		openDatabase();
		// az adott körre frissítjük a játékosnevek listáját
		playerNames = names;
		// ha nem létezik a játékos még, hozzáadjuk (kap ID-t)
		// kiírjuk debugoláshoz a neveket
		// Controller.debugWindow.printMessage("names:");
		for (String n : names) {
			if (n != null) {
				if (!isPlayerExists(n)) {
					addPlayer(n);
				}
			}
		}
		try {
			Statement statement = connection.createStatement();
			// Asztal ellenőrzése itt
			int tableID = getTableNumber(playerNames);

			ResultSet tableIDResult = statement.executeQuery(String.format(
					"SELECT tableID FROM pokertable" + " WHERE tableID = %d",
					tableID));
			// beszúrjuk az új asztalt, ha nincs
			if (!tableIDResult.next()) {
				for (int i = 0; i < 10; i++) {
					if (names[i] != null) {
						statement
								.executeUpdate(String
										.format("insert into"
												+ " pokertable(tableID,playerID,seatNumber) values"
												+
												// játékosonként beszúrás
												// (székszám = index)
												"(%d,%d,%d)", tableID,
												getIDFromName(names[i]), i));
					}
				}
			}
			statement.executeUpdate(String.format(
					"insert into game(tableID,buttonID) values(%d,%d)",
					// az ID-t a név alapján kérjük le, a nevet a hely alapján
					tableID, getIDFromName(playerNames[button])));
			// az aktuális játék értékét inkrementáljuk
			// Az aktuális játék beállítása: az előző számát le kell kérni
			ResultSet maxGame = statement
					.executeQuery("SELECT MAX(gameID) AS maxID FROM game;");
			// az eredményhalmazból a maximumérték kiválasztása
			int n = maxGame.getInt(1);
			thisGameID = n;
		} catch (SQLException e) {
			e.printStackTrace();
			Controller.debugWindow.printMessage(e.getMessage());
		} finally {
			closeDatabase();
		}

	}

	/**
	 * Beállítja a győztest egy játékban
	 * 
	 * @param winnerID
	 */
	void setWinner(String winnerName) {
		openDatabase();
		try {
			Statement statement = connection.createStatement();
			ResultSet res = statement.executeQuery(String.format(
					"select handID"
							+ " from hand where gameID = %d and playerID = %d",
					thisGameID, getIDFromName(winnerName)));
			// beállítjuk a nevet, és a győztes kéz ID-jét az előző lekérés
			// alapján
			statement.executeUpdate(String.format("update game set winnerID "
					+ "= %d, winningHand = %d where gameID = %d",
					getIDFromName(winnerName), res.getInt(1), thisGameID));

		} catch (SQLException e) {
			Controller.debugWindow.printMessage(e.getMessage());
		} finally {
			closeDatabase();
		}
	}

	/**
	 * Beállítja a jelenlegi játék board-ját
	 * 
	 * @param cards
	 *            a board aktuális értéke
	 * 
	 */
	void setBoard(int[] cards) {
		openDatabase();
		Statement statement;
		
		for(int i = 0; i<board.length; i++){
			//a játéktól lekért lapok 1-től indexelődnek...
			board[i] = cards[i+1];
		}
		
		try {
			statement = connection.createStatement();
			// hozzáadjuk egy sztringhez a lapokat
			String s = "";
			for (int i : cards) {
				s = s.concat(String.valueOf(i) + " ");
			}
			statement.executeUpdate(String.format(
					"update game set board = '%s' where gameID = %d", s,
					thisGameID));
		} catch (SQLException e) {
			// debug üzenet..
			Controller.debugWindow.printMessage("Cannot add board!");
			Controller.debugWindow.printMessage(e.getMessage());
		} finally {
			closeDatabase();
		}
	}

	/**
	 * Adott játékos lapjait frissítő függvény, ha kártyákat mutat
	 * 
	 * @param pos
	 *            A játékos pozíciója
	 * @param c1
	 *            A játékos első lapja
	 * @param c2
	 *            A játékos második lapja
	 */
	void addPlayerCards(int pos, int c1, int c2) {
		openDatabase();
		// ha mi vagyunk azok, beállítjuk a lapjainkat
		if (pos == ourSeat) {
			this.c1 = c1;
			this.c2 = c2;
		}
		try {
			statement = connection.createStatement();
			statement.executeUpdate(String.format(
					"insert into hand (playerID,cards,gameID) "
					// lekérjük a játékos IDját, és összefűzzük a lapok számát
					// sztringbe
							+ "values (%d,'%s',%d)",
					getIDFromName(playerNames[pos]),
					(String.valueOf(c1) + " ").concat(String.valueOf(c2)),
					thisGameID));
		} catch (SQLException e) {
			Controller.debugWindow.printMessage(e.getMessage());
			Controller.debugWindow.printMessage("Cannot add cards!");
		} finally {
			closeDatabase();
		}

	}

	/**
	 * A játékosok alapján asztalszámot visszaadó függvény </br> A játékosok
	 * SORRENDJE és HALMAZA különbözteti meg az asztalt, a székszám nem
	 * 
	 * @param names
	 *            A játékosok nevei az asztalnál levő sorrendben
	 * @return Az asztal száma
	 */
	public int getTableNumber(String[] names) {
		// a játékosok neveit tároló lista
		LinkedList<String> playerNamelist = new LinkedList<String>();
		for (String s : names) {
			if (s != null)
				playerNamelist.add(s);
		}
		// megkeressük a legkisebb elemet (abc-sorrend)
		String min = Collections.min(playerNamelist);
		while (!min.equals(playerNamelist.peek())) {
			// addig shifteljük, amíg az eleje lesz a legkisebb elem
			playerNamelist.addFirst(playerNamelist.removeLast());
		}
		String concat = "";
		// sztringek összefűzése
		for (String n : playerNamelist) {
			concat += n;
		}
		// MD5-öt számolnk a sztringre, ehhez objektum lekérése
		MessageDigest digest = null;

		try {
			digest = MessageDigest.getInstance("MD5");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		// MD5 számolása, majd ennek első 4 bájtjának intté alakítása
		// ez az asztal ID-je!
		digest.update(concat.getBytes());
		int tableNumber = ByteBuffer.wrap(digest.digest()).getInt();
		// ne legyenek ronda számok
		return Math.abs(tableNumber);
	}

	/**
	 * 
	 * Visszaad tanítópontokat egy játékoshoz
	 * 
	 * @param playerName
	 *            A játékos, akihez tanítópontokat szeretnénk kérni
	 * @return A tanítópontok a játékoshoz
	 */
	SVMClassifierData[] getPoints(String playerName, int numberOfPoints) {
		return new SVMClassifierData[0];
	}

	public double getHandStrenght(int playerID) {
		openDatabase();
		double jv = 0;
		double jsz = 0;
		try {
			statement = connection.createStatement();
			ResultSet jatszott = statement
					.executeQuery(String
							.format("select count(distinct gameID) "
									+ "from event where playerID = %d",
									playerID));
			if(jatszott.next())
				jsz = jatszott.getDouble(1);
			ResultSet jatekbavolt = statement
					.executeQuery(String
							.format("select count(distinct gameID) from  event where (playerID = %d" 
									+ " and 1 < (select count(distinct eventID) from event where playerID = %d))"
									+ " or (playerID = %d and eventID = 5)"
									+ " or(playerID = %d and eventID = 6)",
									playerID, playerID, playerID, playerID));
			if(jatekbavolt.next())
				jv = jatekbavolt.getDouble(1);
			return (double) jv / jsz;
		} catch (SQLException e) {
			Controller.debugWindow
					.printMessage("kézerősség meghatározásnél hiba");
			Controller.debugWindow.printMessage(e.getMessage());
		} finally {
			closeDatabase();
		}
		return 0;
	}

}
